package com.study.singleton;


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 饿汉式单例模式
 */
public class HungrySingletonTest {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class; //不会触发静态代码块输出 这行代码是加载HungrySingleton类
        HungrySingleton instance = HungrySingleton.getInstance(); //会触发静态代码块输出

        //虽然是单例，但是还是可以通过反射来拿到实例的
        Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
        //表示可以拿到private修饰的
        declaredConstructor.setAccessible( true );
        //点到instance的源码里面看,可以看到类型是枚举类型的话会直接抛出异常，所以为什么推荐使用枚举,因为每个枚举值都是本身枚举类的一个单例实例,其次不能通过反射得到另一个实例
        HungrySingleton instance1 = declaredConstructor.newInstance();
        //这样就已经破坏了 单例的初衷 一个是我们通过类初始化的对象 一个是我们通过反射去调用类的构造函数拿到的对象 所以肯定是false
        System.out.println(instance1 == instance); // false

    }

}


class HungrySingleton{

    //注意 : 只有在真正的初始化完成的时候才会执行静态代码块或加载静态方法/属性
    //类加载只是初始化的一小部分，所以类加载的时候类不算初始化完成
    //什么时候会触发类的初始化？ 主动使用对应的类时,才会触发执行(初始化) 比如new 或者访问一个类的静态成员变量/静态方法
    //类被JVM初始化的时候其实是经过多个步骤的  具体自己去看jvm的类加载机制 可以参考以下链接
    //  Java类加载的过程原理  https://blog.csdn.net/weixin_44538469/article/details/124284889
    private static HungrySingleton instance = new HungrySingleton();

    static {
        System.out.println("HungrySingleton");
    }

    //禁止在外面new
    private HungrySingleton(){

        //因为反射是通过走构造函数来获取实例的,然后类的初始化完成后会执行静态代码块,肯定是比构造函数先执行的所以这里直接判断非空即可不用担心可见性/多线程问题
        //所以我们只需要通过构造函数里面做一个限制就可以了
        if(instance!=null){
            throw new RuntimeException("单例不允许创建多个实例");
        }
    }

    public static HungrySingleton getInstance(){
        return instance;
    }

}